package com.igiantpanda.btserver;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.os.Build;
import android.os.ParcelUuid;
import android.util.Log;

import androidx.annotation.RequiresApi;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class BleManager {
    private static final String TAG = "BleManager";

    private static final String SERVICE_UUID = "11112222-3333-4444-5555-666677778888";
    private static final String CHARACTER_UUID_LENGTH = "11110001-3333-4444-5555-666677778888";
    private static final String CHARACTER_UUID_WIDTH = "11110002-3333-4444-5555-666677778888";

    private Context mContext;
    private BleManager sInstance;
    private BluetoothManager mBtManager;
    private BluetoothAdapter mBtAdapter;
    private BluetoothGattServer mGattServer;
    private ScanResult mScanResult;
    private byte mLength = 0;
    private byte mWitdh = 0;

    private BluetoothGatt mGatt;

    private BluetoothGattServerCallback mGattServerCb = new BluetoothGattServerCallback() {
        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            Log.d(TAG, "onConnectionStateChange");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "status:" + status);
            Log.d(TAG, "newState:" + newState);
            super.onConnectionStateChange(device, status, newState);
        }

        @Override
        public void onServiceAdded(int status, BluetoothGattService service) {
            Log.d(TAG, "onServiceAdded");
            Log.d(TAG, "status:" + status);
            Log.d(TAG, "service:" + service);
            super.onServiceAdded(status, service);
        }

        @Override
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
            Log.d(TAG, "onCharacteristicReadRequest");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "requestId:" + requestId);
            Log.d(TAG, "offset:" + offset);
            Log.d(TAG, "characteristic:" + characteristic);
            super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
        }

        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            Log.d(TAG, "onCharacteristicWriteRequest");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "requestId:" + requestId);
            Log.d(TAG, "characteristic:" + characteristic);
            Log.d(TAG, "preparedWrite:" + preparedWrite);
            Log.d(TAG, "responseNeeded:" + responseNeeded);
            Log.d(TAG, "offset:" + offset);
            Log.d(TAG, "value:" + value.toString());
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
        }

        @Override
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
            Log.d(TAG, "onDescriptorReadRequest");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "requestId:" + requestId);
            Log.d(TAG, "offset:" + offset);
            Log.d(TAG, "descriptor:" + descriptor);
            super.onDescriptorReadRequest(device, requestId, offset, descriptor);
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, descriptor.getValue());
        }

        @Override
        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            Log.d(TAG, "onDescriptorWriteRequest");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "requestId:" + requestId);
            Log.d(TAG, "preparedWrite:" + preparedWrite);
            Log.d(TAG, "responseNeeded:" + responseNeeded);
            Log.d(TAG, "offset:" + offset);
            Log.d(TAG, "value:" + value.toString());
            super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
        }

        @Override
        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
            Log.d(TAG, "onExecuteWrite");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "requestId:" + requestId);
            Log.d(TAG, "execute:" + execute);
            super.onExecuteWrite(device, requestId, execute);
//            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, )
        }

        @Override
        public void onNotificationSent(BluetoothDevice device, int status) {
            Log.d(TAG, "onNotificationSent");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "status:" + status);
            super.onNotificationSent(device, status);
        }

        @Override
        public void onMtuChanged(BluetoothDevice device, int mtu) {
            Log.d(TAG, "onMtuChanged");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "mtu:" + mtu);
            super.onMtuChanged(device, mtu);
        }

        @Override
        public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
            Log.d(TAG, "onPhyUpdate");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "txPhy:" + txPhy);
            Log.d(TAG, "rxPhy:" + rxPhy);
            Log.d(TAG, "status:" + status);
            super.onPhyUpdate(device, txPhy, rxPhy, status);
        }

        @Override
        public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
            Log.d(TAG, "onPhyRead");
            Log.d(TAG, "device:" + device);
            Log.d(TAG, "txPhy:" + txPhy);
            Log.d(TAG, "rxPhy:" + rxPhy);
            Log.d(TAG, "status:" + status);
            super.onPhyRead(device, txPhy, rxPhy, status);
        }
    };

    public BleManager(Context context) {
        mContext = context;
        mBtManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        mBtAdapter = mBtManager.getAdapter();
        initGattServer();
    }

//    public BleManager getInstance(Context ) {
//        if (null == sInstance) {
//            sInstance = new BleManager();
//        }
//
//        return sInstance;
//    }

    private void initGattServer() {
        BluetoothGattCharacteristic characterLength =
                new BluetoothGattCharacteristic(UUID.fromString(CHARACTER_UUID_LENGTH),
                                                BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
                                                BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
        BluetoothGattCharacteristic characterWidth =
                new BluetoothGattCharacteristic(UUID.fromString(CHARACTER_UUID_WIDTH),
                        BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
                        BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
        BluetoothGattService gattService = new BluetoothGattService(UUID.fromString(SERVICE_UUID), BluetoothGattService.SERVICE_TYPE_PRIMARY);

        characterLength.setValue(new byte[]{10, 20});
        characterLength.setValue(new byte[]{30, 40});
        gattService.addCharacteristic(characterLength);
        gattService.addCharacteristic(characterWidth);

        mGattServer = mBtManager.openGattServer(mContext, mGattServerCb);
        mGattServer.addService(gattService);
    }

    private AdvertiseSettings buildAdvertiseSettings(int timeout) {
        AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();

        return  builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) // default ADVERTISE_MODE_LOW_POWER
                        .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) // default ADVERTISE_TX_POWER_MEDIUM
                        .setConnectable(true) // default true
                        .setTimeout(timeout) // default 0, no limit
                        .build();
    }

    private AdvertiseData buildAdvertiseData() {
        byte[] data = {1, 2, 3, 4, 5, 6};

        return new AdvertiseData.Builder()
//                                .setIncludeDeviceName(true)
//                                .setIncludeTxPowerLevel(true)
                                .addServiceUuid(ParcelUuid.fromString(SERVICE_UUID))
//                                .addServiceData(ParcelUuid.fromString(SERVICE_UUID), data) // it will lead advertise failure
                                .build();
    }

    public void startAdvertise(int timeout) {
        BluetoothLeAdvertiser bleAdvertiser = mBtAdapter.getBluetoothLeAdvertiser();
        bleAdvertiser.startAdvertising(buildAdvertiseSettings(timeout),
                buildAdvertiseData(),
                new AdvertiseCallback() {
                    @Override
                    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
                        super.onStartSuccess(settingsInEffect);
                        Log.d(TAG, "startAdvertise success");
                    }

                    @Override
                    public void onStartFailure(int errorCode) {
                        super.onStartFailure(errorCode);
                        Log.d(TAG, "startAdvertise failure:" + errorCode);
                    }
                });
    }

    public void startScan(int timeout) {
        BluetoothLeScanner scanner = mBtAdapter.getBluetoothLeScanner();
        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(SERVICE_UUID)).build();
        ArrayList<ScanFilter> filterList = new ArrayList<>();
        ScanSettings settings = new ScanSettings.Builder().build();

        filterList.add(filter);

        Log.d(TAG, "startScan");
        mScanResult = null;
        scanner.startScan(filterList, settings, new ScanCallback() {
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                Log.d(TAG, "onScanResult result:" + result);
                super.onScanResult(callbackType, result);
                mScanResult = result;
            }

            @Override
            public void onBatchScanResults(List<ScanResult> results) {
                Log.d(TAG, "onBatchScanResults result:" + results.toString());
                super.onBatchScanResults(results);
            }

            @Override
            public void onScanFailed(int errorCode) {
                Log.d(TAG, "onScanResult errorCode:" + errorCode);
                super.onScanFailed(errorCode);
            }
        });
    }

    public void connectDevice() {
        if (null == mScanResult) {
            Log.e(TAG, "connectDevice Error:No device");
            return;
        }

        mGatt = mScanResult.getDevice().connectGatt(mContext, true, new BluetoothGattCallback() {
            @Override
            public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
                super.onPhyUpdate(gatt, txPhy, rxPhy, status);
                Log.d(TAG, "onPhyUpdate");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "txPhy:" + txPhy);
                Log.d(TAG, "rxPhy:" + rxPhy);
                Log.d(TAG, "status:" + status);
            }

            @Override
            public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
                super.onPhyRead(gatt, txPhy, rxPhy, status);
                Log.d(TAG, "onPhyRead");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "txPhy:" + txPhy);
                Log.d(TAG, "rxPhy:" + rxPhy);
                Log.d(TAG, "status:" + status);
            }

            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                super.onConnectionStateChange(gatt, status, newState);
                Log.d(TAG, "onConnectionStateChange");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "status:" + status);
                Log.d(TAG, "newState:" + newState);

                if (BluetoothGatt.GATT_SUCCESS == status && BluetoothProfile.STATE_CONNECTED == newState) {
                    mGatt.discoverServices();
                }
            }

            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                super.onServicesDiscovered(gatt, status);
                Log.d(TAG, "onServicesDiscovered");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "status:" + status);

                if (BluetoothGatt.GATT_SUCCESS == status) {
                    BluetoothGattService gattService = mGatt.getService(UUID.fromString(SERVICE_UUID));
                    if (null != gattService) {
                        BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(UUID.fromString(CHARACTER_UUID_LENGTH));
                    }
                }
            }

            @Override
            public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicRead(gatt, characteristic, status);
                Log.d(TAG, "onCharacteristicRead");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "characteristic:" + characteristic);
                Log.d(TAG, "status:" + status);
            }

            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicWrite(gatt, characteristic, status);
                Log.d(TAG, "onCharacteristicWrite");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "characteristic:" + characteristic);
                Log.d(TAG, "status:" + status);

            }

            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicChanged(gatt, characteristic);
                Log.d(TAG, "onCharacteristicChanged");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "characteristic:" + characteristic);
            }

            @Override
            public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                super.onDescriptorRead(gatt, descriptor, status);
                Log.d(TAG, "onDescriptorRead");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "status:" + status);
            }

            @Override
            public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                super.onDescriptorWrite(gatt, descriptor, status);
                Log.d(TAG, "onDescriptorWrite");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "descriptor:" + descriptor);
                Log.d(TAG, "status:" + status);
            }

            @Override
            public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
                super.onReliableWriteCompleted(gatt, status);
                Log.d(TAG, "onReliableWriteCompleted");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "status:" + status);
            }

            @Override
            public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
                super.onReadRemoteRssi(gatt, rssi, status);
                Log.d(TAG, "onReadRemoteRssi");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "rssi:" + rssi);
                Log.d(TAG, "status:" + status);
            }

            @Override
            public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
                super.onMtuChanged(gatt, mtu, status);
                Log.d(TAG, "onMtuChanged");
                Log.d(TAG, "gatt:" + gatt);
                Log.d(TAG, "mtu:" + mtu);
                Log.d(TAG, "status:" + status);
            }
        }, BluetoothDevice.TRANSPORT_LE);
    }

    public void readCharacteristics() {
        if (null == mGatt) {
            Log.e(TAG, "readCharacteristics Error:no GATT");
            return;
        }

        BluetoothGattService gattService = mGatt.getService(UUID.fromString(SERVICE_UUID));
        if (null != gattService) {
            Log.d(TAG, "readCharacteristics");
            BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(UUID.fromString(CHARACTER_UUID_LENGTH));
            mGatt.readCharacteristic(characteristic);
        }
    }

    public void writeCharacteristics() {
        if (null == mGatt) {
            Log.e(TAG, "readCharacteristics Error:no GATT");
            return;
        }

        BluetoothGattService gattService = mGatt.getService(UUID.fromString(SERVICE_UUID));
        if (null != gattService) {
            Log.d(TAG, "writeCharacteristics");
            BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(UUID.fromString(CHARACTER_UUID_LENGTH));
            characteristic.setValue(new byte[]{(byte)(mLength + 1), (byte)(mLength + 2)});
            mGatt.writeCharacteristic(characteristic);
        }
    }

}
